Hilt 扩展 | MAD Skills
Hilt
https://dagger.dev/hilt/
如果您更喜欢通过视频了解此内容,可以在此处查看:
案例: WorkManager 扩展
Hilt 扩展是一个生成代码的库,常通过注解处理器实现。生成的代码作为构成 Hilt 依赖项注入关系图的模块或入口点。
Jetpack 中 WorkManager 的集成库就是一个扩展的例子。WorkManager 扩展帮助我们减少向 worker 提供依赖项时所需的模板代码及配置。该库由两部分组成,分别为 androidx.hilt:hilt-work 和 androidx.hilt:hilt-compiler。第一部分包含 HiltWorker 注解以及一些运行时的辅助类,第二部分是一个注解处理器,根据第一部分中注解提供的信息生成模块。
Jetpack 中 WorkManager
https://developer.android.google.cn/training/dependency-injection/hilt-jetpack#workmanager
扩展的使用非常简单,仅需在您的 worker 上添加 @HiltWorker 注解:
@HiltWorker
public class ExampleWorker extends Worker {
// ...
}
扩展编译器会生成一个添加了 @Module 注解的类:
@Generated("androidx.hilt.AndroidXHiltProcessor")
@Module
@InstallIn(SingletonComponent.class)
@OriginatingElement(
topLevelClass = ExampleWorker.class
)
public interface ExampleWorker_HiltModule {
@Binds
@IntoMap
@StringKey("my.app.ExmapleWorker")
WorkerAssistedFactory<? extends ListenableWorker> bind(
ExampleWorker_AssistedFactory factory);
}
该模块为 worker 定义了一个可以访问 HiltWorkerFactory 的绑定。然后,配置 WorkerManager 使用该 factory,从而使 worker 的依赖项注入可用。
Hilt 聚合
由于 Hilt 具有聚合能力,任何通过添加 @InstallIn 注解生成 @Module 及 @EntryPoint 的工具都可以被 Hilt 发现,并在编译期成为 Hilt DI 图中的一部分。这使得扩展可以轻松地以插件形式集成到 Hilt,无需开发者处理任何额外工作。
注解处理器
生成代码的常规途径是使用注解处理器。源文件转换为 class 文件之前,注解处理器会在编译器中运行。当资源带有处理器所声明的已支持的注解时,处理器会进行处理。处理器可以生成进一步需要被处理的方法,因此编译器会不断循环运行注解处理器,直到没有新的内容产生。一旦所有的环节都完成,编译器才会将源文件转换为 class 文件。
由于循环机制,处理器可以相互作用。这非常重要,因为这使得 Hilt 的注解处理器可以处理由其他处理器生成的 @Module 或 @EntryPoint 类。这也意味着您的扩展也可以建立在其他人编写的扩展之上!
WorkManager extension processor 根据带有 @HiltWorker 注解的类生成代码,同时验证注解用法并使用 JavaPoet 等库生成代码。
WorkManager extension processor
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/JavaPoet
https://github.com/square/javapoet
Hilt 扩展注解
Hilt API 中有两个重要的注解: @GeneratesRootInput 和 @OriginatingElement。扩展应该使用这些注解才能与 Hilt 正确集成。
扩展应该使用 @GeneratesRootInput 来启用代码生成的注解。这让 Hilt 注解处理器知道它应该在生成组件之前完成扩展注解处理器的工作。例如,@HiltWorker 注解本身是被 @GeneratesRootInput 注解修饰的:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@GeneratesRootInput
public @interface HiltWorker {
}
@HiltAndroidTest
class SampleTest {
@HiltWorker
class TestWorker extends Worker {
// …
}
}
@Module
@InstallIn(SingletonComponent.class)
@OriginatingElement(
topLevelClass = SampleTest.class
)
public interface SampleTest_TestWorker__HiltModule {
// …
}
心得
项目中的通用模式
如果您的项目中有创建模块或入口点的通用模式,那么它们很大概率可以通过使用 Hilt 扩展实现自动化。举个例子,如果每一个实现特定接口的类都必须创建一个具有多绑定的模块,那么可以创建一个扩展,只需在实现类上添加注解即可生成多重绑定模块。
支持非标准成员注入
ServiceLoader
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html
镜像绑定
有时需要使用不同的限定符来镜像或重新声明绑定。当存在自定义组件时,这可能更常见。为了避免丢失重新声明的绑定,可以创建 Hilt 扩展以自动生成其他镜像绑定的模块。例如,考虑包含不同依赖项实现的应用中 "付费" 和 "免费" 订阅的情况。然后,每一层都有两个不同的自定义组件,这样您就可以确定依赖关系的作用域。当添加一个通用的未限定作用域的绑定时,定义绑定的模块可以在其 @InstallIn 中包含两个组件,也可以加载在父组件中,通常是单例组件。但是当绑定被限定作用域时,模块必须被复制,因为需要不同的限定符。实现一个扩展就可以生成两个模块,可以避免样板代码并确保不会遗漏通用绑定。
总结
Hilt 的扩展可以进一步增强代码库中的依赖项注入能力,因为它们可以实现与 Hilt 尚不支持的其他库集成。总而言之,扩展通常由两部分组成,包含扩展注解的运行时部分,以及生成 @Module 或 @EntryPoint 的代码生成器 (通常是注解处理器)。扩展的运行时部分可能有额外的辅助类,这些辅助类使用声明在生成的模块或入口点中绑定。代码生成器还可能生成与扩展相关的附加代码,它们无需专门生成模块和入口点。
扩展必须使用两个注解才能与 Hilt 正确交互:
@GeneratesRootInput 添加在扩展注解上。
@OriginatingElement 由扩展添加在生成的模块或入口点上。
最后,您可以查看 hilt-install-binding 项目,这是一个简单扩展的示例,它展示了本文中提到的概念:
Hilt - MAD Skills 播放列表
https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_9Qo-RBRYhVmME1iR4oeTK
欢迎您通过下方二维码向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的支持!
推荐阅读